#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include "../src/cell.h"
#include "../src/cell_func.h"
#include "../src/utils.h"

#define MAX_ARGS_COUNT 10
#define MAX_CHARS_COUNT 50
#define MAX_COMMANDS_COUNT 10

enum cmd {
    UNSET,
    WRITE,               // write,   w    - [string file]
    QUIT,                // quit,    q

    GET_CELL,            // getcell, gc   - <int x>     <int y>
    DRAW_WALL,           // wall          - <int x>     <int y>
    DRAW_PORTAL,         // portal       - <int x>     <int y>
    DELETE,              // del, d        - <int x>     <int y>
    TIME,                // time          - <set,get>   [int value]
    DAY_LENGTH,          // daylength     - <set,get>   [int value]
    MAX_ENERGY,          // maxenergy     - <set,get>   [int value]
    FOOD_MAX_AGE,        // foodmaxage    - <set,get>   [int value]
    CELL_MAX_AGE,        // cellmaxage    - <set,get>   [int value]
    SIZE,                // size          - <set,get>   [int width] [int heigh]
    EDIT_CELL,           // editcell      - <int index> *
    MUTATION_CHANCE,     // mutationchance, mchance - <set,get> [int value]
    GET_RAW,             // getraw        - <int x,y>
    SET_RAW,             // setraw        - <int x,y> <...>
    HELP,
    UNKNOWN,
};

enum ARG_TYPE {
    NON,
    GET,
    SET,
    NUM,
};

struct {
    enum cmd command;
    char names[MAX_COMMANDS_COUNT][MAX_CHARS_COUNT];
    unsigned namesCount;
} cmdList[UNKNOWN+1];

struct Command {
    enum cmd command;
    char rawCommand[MAX_CHARS_COUNT];
    unsigned argc;
    char argv[MAX_ARGS_COUNT][MAX_CHARS_COUNT];
};

struct CommandList {
    struct Command commands[MAX_COMMANDS_COUNT];
    int length;
};

struct Command parseCommand(char rawcmd[MAX_ARGS_COUNT*MAX_CHARS_COUNT]);
void runCommand(struct World *w, struct Command command);
void registerCommand(enum cmd command, char** names);
bool isDigits(char str[MAX_CHARS_COUNT]);
enum cmd getCommandType(char command[]);
enum ARG_TYPE getArgType(char* str);
void invalidUsage(char* command);

static bool running;
char filepath[MAX_PATH_LENGTH];

int main(int argc, char **argv) {
    static struct World world;
    if(argc == 2) {
        importWorld(argv[1], &world);
        strcpy(filepath, argv[1]);
    } else {
        strcpy(filepath, "world.bin");
    }

    registerCommand(MUTATION_CHANCE, (char*[]){"mutationchance", "mchance", NULL});
    registerCommand(DAY_LENGTH,      (char*[]){"daylength",      "dl",      NULL});
    registerCommand(GET_CELL,        (char*[]){"getcell",        "gc",      NULL});
    registerCommand(WRITE,           (char*[]){"write",          "w",       NULL});
    registerCommand(QUIT,            (char*[]){"quit",           "q",       NULL});
    registerCommand(DELETE,          (char*[]){"del",            "d",       NULL});
    registerCommand(CELL_MAX_AGE,    (char*[]){"cellmaxage",                NULL});
    registerCommand(FOOD_MAX_AGE,    (char*[]){"foodmaxage",                NULL});
    registerCommand(MAX_ENERGY,      (char*[]){"maxenergy",                 NULL});
    registerCommand(EDIT_CELL,       (char*[]){"editcell",                  NULL});
    registerCommand(DRAW_WALL,       (char*[]){"wall",                      NULL});
    registerCommand(DRAW_PORTAL,     (char*[]){"portal",                    NULL});
    registerCommand(HELP,            (char*[]){"help",                      NULL});
    registerCommand(TIME,            (char*[]){"time",                      NULL});
    registerCommand(SIZE,            (char*[]){"size",                      NULL});
    registerCommand(GET_RAW,         (char*[]){"getraw",                    NULL});

    struct Command command;
    struct CommandList cmdlist;
    char rawcmd[MAX_ARGS_COUNT*MAX_CHARS_COUNT];
    do {
        if(isatty(fileno(stdin)))
            printf("> ");
        running = (fgets(rawcmd, MAX_ARGS_COUNT*MAX_CHARS_COUNT, stdin) != NULL);
        if(!running) break;
        rawcmd[strlen(rawcmd)-1] = '\0';
        if(!strlen(rawcmd)) continue;
        cmdlist = (const struct CommandList){ 0 };
        command = parseCommand(rawcmd);
        if(command.command == UNKNOWN) {
            bool isCommand = true;
            for(int i=0; i<strlen(command.rawCommand); i++) {
                struct Command newCommand;
                newCommand.command = getCommandType((char[2]){ command.rawCommand[i], 0 });
                if(newCommand.command == UNKNOWN) isCommand = false;
                cmdlist.commands[cmdlist.length] = newCommand;
                cmdlist.length++;
            }
            if(!isCommand) {
                cmdlist.commands[0] = (struct Command){.command = UNKNOWN};
                memcpy(cmdlist.commands[0].rawCommand, command.rawCommand, sizeof(char)*MAX_CHARS_COUNT);
                cmdlist.length = 1;
            }
        } else {
            cmdlist.commands[0] = command;
            cmdlist.length = 1;
        }
        for(int i=0; i<cmdlist.length; i++) {
            command = cmdlist.commands[i];
            runCommand(&world, command);
        }
    } while(running);
    return(0);
}

bool isDigits(char str[MAX_CHARS_COUNT]) {
    for(int i=0; i<strlen(str); i++) {
        if(!isdigit(str[i])) {
            return(false);
        }
    }
    return(true);
}

void runCommand(struct World *w, struct Command command) {
    int x, y;
    switch (command.command) {
    case(WRITE):
        if(command.argc == 1) {
            strcpy(filepath, command.argv[0]);
        } else if(command.argc > 1) {
            invalidUsage(command.rawCommand);
            break;
        }
        exportWorld(w);
        break;
    case(QUIT):
        running = false;
        break;
    case(SIZE):
        if(command.argc == 1 && getArgType(command.argv[0]) == GET) {
            printf("%d %d\n", w->width, w->height);
        } else if(command.argc == 3 && getArgType(command.argv[0]) == SET && getArgType(command.argv[1]) == NUM && getArgType(command.argv[2]) == NUM) {
            w->width = atoi(command.argv[1]);
            w->height = atoi(command.argv[2]);
        } else {
            invalidUsage(command.rawCommand);
        }
        break;
    case(GET_CELL):
        if(command.argc != 2 || getArgType(command.argv[0]) != NUM || getArgType(command.argv[1]) != NUM) {
            invalidUsage(command.rawCommand);
            break;
        }
        x = atoi(command.argv[0]);
        y = atoi(command.argv[1]);
        printInfoAboutCell(w, x, y);
        break;
    case(MUTATION_CHANCE):
        if(command.argc == 0) {
            invalidUsage(command.rawCommand);
            break;
        }
        if(!strcmp(command.argv[0], "get")) {
            printf("%d\n", w->mutationChance);
            break;
        } else if(!strcmp(command.argv[0], "set") && isDigits(command.argv[1])) {
            w->mutationChance = atoi(command.argv[1]);
            break;
        } else {
            invalidUsage(command.rawCommand);
            break;
        }
        break;
    case(DRAW_WALL):
        if(command.argc != 2 || !isDigits(command.argv[0]) || !isDigits(command.argv[1])) {
            invalidUsage(command.rawCommand);
            break;
        }
        x = atoi(command.argv[0]);
        y = atoi(command.argv[1]);
        w->cells[cordsToIndex(w, x, y)].type = T_WALL;
        break;
    case(DRAW_PORTAL):
        if(command.argc != 2 || !isDigits(command.argv[0]) || !isDigits(command.argv[1])) {
            invalidUsage(command.rawCommand);
            break;
        }
        x = atoi(command.argv[0]);
        y = atoi(command.argv[1]);
        w->cells[cordsToIndex(w, x, y)].type = T_PORTAL;
        break;
    case(DELETE):
        if(command.argc != 2 || !isDigits(command.argv[0]) || !isDigits(command.argv[1])) {
            invalidUsage(command.rawCommand);
            break;
        }
        x = atoi(command.argv[0]);
        y = atoi(command.argv[1]);
        w->cells[cordsToIndex(w, x, y)].type = T_NOTHING;
        break;
    case(DAY_LENGTH):
        if(command.argc == 1 && !strcmp(command.argv[0], "get")) {
            printf("%d\n", w->dayLength);
            break;
        } else if(command.argc == 2 && !strcmp(command.argv[0], "set") && isDigits(command.argv[1])) {
            w->dayLength = atoi(command.argv[1]);
            break;
        } else {
            invalidUsage(command.rawCommand);
            break;
        }
        break;
    case(MAX_ENERGY):
        if(command.argc == 1 && !strcmp(command.argv[0], "get")) {
            printf("%d\n", w->maxEnergy);
            break;
        } else if(command.argc == 2 && !strcmp(command.argv[0], "set") && isDigits(command.argv[1])) {
            w->maxEnergy = atoi(command.argv[1]);
            break;
        } else {
            invalidUsage(command.rawCommand);
            break;
        }
        break;
    case(FOOD_MAX_AGE):
        if(command.argc == 1 && !strcmp(command.argv[0], "get")) {
            printf("%d\n", w->foodMaxAge);
            break;
        } else if(command.argc == 2 && !strcmp(command.argv[0], "set") && isDigits(command.argv[1])) {
            w->mutationChance = atoi(command.argv[1]);
            break;
        } else {
            invalidUsage(command.rawCommand);
            break;
        }
        break;
    case(CELL_MAX_AGE):
        if(command.argc == 1 && !strcmp(command.argv[0], "get")) {
            printf("%d\n", w->maxAge);
        } else if(command.argc == 2 && !strcmp(command.argv[0], "set") && isDigits(command.argv[1])) {
            w->maxAge = atoi(command.argv[1]);
        } else {
            invalidUsage(command.rawCommand);
        }
        break;
    case(HELP):
        printf("I'm to lazy to write manuals at the moment, so you can check the source code\n");
        break;
    case(EDIT_CELL):
        puts("(none)");
        break;
    case(TIME):
        if(command.argc == 1 && !strcmp(command.argv[0], "get"))
            printf("%d\n", w->time);
        else if(command.argc == 2 && !strcmp(command.argv[0], "set") && isDigits(command.argv[1]))
            w->time = atoi(command.argv[1]);
        else
            invalidUsage(command.rawCommand);
        break;
    case(GET_RAW):
        if(command.argc != 2 || !isDigits(command.argv[0]) || !isDigits(command.argv[1])) {
            invalidUsage(command.rawCommand);
            break;
        }
        x = atoi(command.argv[0]);
        y = atoi(command.argv[1]);
        struct Cell cell = w->cells[cordsToIndex(w, x, y)];
        for(int i=0; i < GENOME_LENGTH; i++)
            printf("%d ", cell.genome[i]);
        printf("\n%d\n", cell.energy);
        printf("%d\n", cell.age);
        printf("%d %d %d\n", cell.color.r, cell.color.g, cell.color.b);
        printf("%d\n", cell.minerals);
        printf("%d\n", cell.rotation);
        printf("%d\n", cell.type);
        break;
    case(SET_RAW):
        break;
    case(UNKNOWN):
        printf("%s: Unknown command! Type `help` to see list of commands.\n", command.rawCommand);
        break;
    case(UNSET):
        break;
    }
}

struct Command parseCommand(char rawcmd[MAX_ARGS_COUNT*MAX_CHARS_COUNT]) {
    int wordIndex=0, inWordIndex=0;
    struct Command output = { 0 };
    for(int i=0; i <= strlen(rawcmd); i++) {
        if(rawcmd[i] == ' ' || rawcmd[i] == '\0') {
            if(wordIndex == 0) {
                output.rawCommand[i] = '\0';
            } else {
                output.argv[wordIndex][inWordIndex] = '\0';
                output.argc++;
            }
            wordIndex++;
            inWordIndex = 0;
            continue;
        }
        if(wordIndex == 0) {
            output.rawCommand[inWordIndex] = rawcmd[i];
        } else {
            output.argv[wordIndex-1][inWordIndex] = rawcmd[i];
        }
        inWordIndex++;
    }
    output.command = getCommandType(output.rawCommand);
    inWordIndex++;
    return(output);
}

enum cmd getCommandType(char command[]) {
    for(int i=0; cmdList[i].command != UNSET; i++) {
        for(int j=0; j < cmdList[i].namesCount; j++) {
            if(!strcmp(cmdList[i].names[j], command)) return(cmdList[i].command);
        }
    }
    return(UNKNOWN);
}

void registerCommand(enum cmd command, char** names) {
    unsigned commandIndex;
    for(commandIndex=0; cmdList[commandIndex].command != UNSET; commandIndex++);
    cmdList[commandIndex].command = command;
    int index = 0;
    for(; names[index] != NULL; index++) {
        strcpy(cmdList[commandIndex].names[index], names[index]);
        cmdList[commandIndex].namesCount++;
    }
    cmdList[commandIndex].namesCount = index;
}

void invalidUsage(char* command) {
    printf("%s: Invalid usage! See `help` for more info\n", command);
}

enum ARG_TYPE getArgType(char* str) {
    if(isDigits(str)) return(NUM);
    else if(!strcmp(str, "get")) return(GET);
    else if(!strcmp(str, "set")) return(SET);
    return(NON);
}
